package org.apache.commons.graph.model

import org.apache.commons.graph.Mapper
import org.apache.commons.graph.Path
import org.apache.commons.graph.bean.DefaultEdge
import org.apache.commons.graph.weight.WeightedPath
import org.apache.commons.graph.weight.Monoid

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
 * Support [WeightedPath] implementation, optimized for algorithms (such Dijkstra's) that need to rebuild the path
 * traversing the predecessor list bottom-up.
 *
 * @param <V> the Graph vertices type
 * @param <WE> the Graph weighted edges type
 * @param <W> the weight type
</W></WE></V> */
class InMemoryWeightedPath<V, WE, W>(start: V, target: V,
                                     private val weightOperations: Monoid<W>,
                                     private val weightedEdges: Mapper<WE, W>
) : InMemoryPath<V, WE>(start, target), WeightedPath<V, WE, W> {

    /**
     * {@inheritDoc}
     */
    override var weight: W
        private set

    /**
     * Creates a new instance of [InMemoryWeightedPath].
     *
     * @param start the start vertex
     * @param target the target vertex
     * @param weightOperations
     * @param weightedEdges
     */
    init {
        this.weight = weightOperations.identity()
    }

    /**
     * {@inheritDoc}
     */
    override fun addConnectionInHead(head: V, edge: WE, tail: V) {
        super.addConnectionInHead(head, edge, tail)
        increaseWeight(edge)
    }

    /**
     * {@inheritDoc}
     */
    override fun addConnectionInTail(head: V, edge: WE, tail: V) {
        super.addConnectionInTail(head, edge, tail)
        increaseWeight(edge)
    }

    /**
     * {@inheritDoc}
     */
    override fun equals(obj: Any?): Boolean {

        if (obj == null) {
            return false
        }

        if (this === obj) {
            return true
        }

        if (!super.equals(obj)) {
            return false
        }

        if (this::class != obj::class) {
            return false
        }

        //val other// test against any WeightedPath typed instance
         obj as InMemoryWeightedPath<*, *, *>
        return weight == obj.weight
    }

    /**
     * {@inheritDoc}
     */
    override fun hashCode(): Int {
        val prime = 31
        var result = super.hashCode()
        result = prime * result + (weight?.hashCode() ?: 0)
        return result
    }

    /**
     * Increase the path weight with the weight of the input weighted edge.
     *
     * @param edge the edge whose weight is used to increase the path weight
     */
    private fun increaseWeight(edge: WE) {
        weight = weightOperations.append(weightedEdges.map(edge), weight)
    }

    /**
     * {@inheritDoc}
     */
    override fun toString(): String {
        return "InMemoryPath [weigth=$weight, vertices=${vertices}, edges=${edges}]"
    }

    companion object {
        private const val serialVersionUID = 7937494144459068796L
    }
}

fun<V, WE, W> Path.Companion.inMemoryWeightedPath(
    start: V, target: V,
    weightOperations: Monoid<W>,
    weightedEdges: Mapper<WE, W>
 ) = InMemoryWeightedPath(start,target, weightOperations, weightedEdges)

fun<V, W> Path.Companion.sampleInMemoryWeightedPath(
    start: V, target: V,
    weightOperations: Monoid<W>,
    weightedEdges: Mapper<DefaultEdge<V>, W>
) = InMemoryWeightedPath(start,target, weightOperations, weightedEdges)